package com.stxx.stiot2.controller;

import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.alibaba.fastjson2.JSONObject;
import com.influxdb.client.InfluxDBClient;
import com.influxdb.client.QueryApi;
import com.influxdb.query.FluxRecord;
import com.influxdb.query.FluxTable;
import com.stxx.framwork.domain.R;
import com.stxx.framwork.domain.model.PageQuery;
import com.stxx.framwork.domain.model.PageResult;
import com.stxx.framwork.exception.ServiceException;
import com.stxx.stiot2.domain.Device;
import com.stxx.stiot2.domain.Terminal;
import com.stxx.stiot2.domain.ext.DeviceSignal;
import com.stxx.stiot2.event.dto.P2tMsg;
import com.stxx.stiot2.external.InfluxdbServer;
import com.stxx.stiot2.gateway.GatewayUtils;
import com.stxx.stiot2.gateway.face.GatewayServer;
import com.stxx.stiot2.service.*;
import lombok.Data;
import org.noear.solon.annotation.*;
import org.noear.solon.core.handle.Context;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.SQLException;
import java.util.*;

@Controller
@Mapping("/open")
public class OpenController {
    @Inject
    TerminalService terminalService;
    @Inject
    ProfileService profileService;
    @Inject
    SysConfigService configService;
    //获取设备列表
    @Inject
    DeviceService baseService;
    @Inject
    AlarmService alarmService;
    @Inject
    GatewayUtils gatewayUtils;
    @Inject
    InfluxdbServer influxdbServer;

    @Get
    @Mapping("/device/list")
    public PageResult<Device> list(Device bo, PageQuery pageQuery) throws SQLException {
        validSign();
        PageResult<Device> r = baseService.pageList(bo,pageQuery);
        return r;
    }
    //获取设备数据点
    @Get
    @Mapping("/device/{code}/signals")
    public R<List<DeviceSignal>> signals(@Path Long code) throws SQLException {
        validSign();
        List<DeviceSignal> r = baseService.getSignals(code);
        return R.ok(r);
    }

    //获取设备数据点
    @Get
    @Mapping("/alarm/{id}/ack")
    public R<List<DeviceSignal>> alarmAck(@Path Long code) throws SQLException {
        validSign();
        List<DeviceSignal> r = baseService.getSignals(code);
        return R.ok(r);
    }

    @Get
    @Mapping("/alarm/{id}/clear")
    public R<List<DeviceSignal>> alarmClear(@Path Long code) throws SQLException {
        validSign();
        List<DeviceSignal> r = baseService.getSignals(code);
        return R.ok(r);
    }

    @Get
    @Mapping("/device/{id}/rpc")
    public R<Void> rpc(@Path String id, String terminalCode,String commandType,String params) throws SQLException {
        validSign();
        Terminal terminal = terminalService.queryById(terminalCode);
        GatewayServer server = gatewayUtils.getGateway(terminal.getCode());
        P2tMsg ptMsg = new P2tMsg();
        ptMsg.setType(commandType);
        ptMsg.setProperties(params);
        try {
            server.send(terminal.getGatewayCode(), terminal.getCode(), ptMsg);
            return R.ok("命令执行成功");
        }catch (Exception e){
            return R.fail("命令执行失败,"+e.getMessage());
        }

    }

    @Get
    @Mapping("/device/{id}/last-signal-values")
    public R<Map<String, Map<String,Object>>> lastValues(@Path Long id) throws SQLException {
        validSign();
        List<Terminal> terminals = terminalService.queryByDeviceId(id);
        Map<String, Map<String,Object>> map = new HashMap<>();
        terminals.forEach(item->{
            map.put(item.getCode(),terminalService.getLastValues(item.getCode()));
        });
        return R.ok(map);
    }


    @Post
    @Mapping("/device/{id}/signal-values")
    public R<Map<String, List<Map<String,Object>>>> historyValues(@Path String id,@Body SignalQueryParams params) throws SQLException {
        if(StrUtil.isEmpty(params.getTerminalCode())){
            return R.ok(null);
        }
        //validSign();
        Terminal terminal = terminalService.queryById(params.getTerminalCode());
//        Set<String> profiles = new HashSet<>();
//        terminals.forEach(item->{
//            profiles.add(item.getProfileCode());
//        });
        Long ts = System.currentTimeMillis();

        String startTime = ((params.startTs - ts)/1000)+"s";
        String endTime = ((params.endTs - ts)/1000)+"s";
        StringBuilder sb = new StringBuilder();
        sb.append("from(bucket: \"stxx\")\n")
                .append("  |> range(start: "+startTime+", stop: "+endTime+")\n")
                .append("  |> filter(fn: (r) => ");

        //过滤表
        //profiles.forEach(item->{
            sb.append(" r[\"_measurement\"] == \""+terminal.getProfileCode()+"\" or");
        //});
        sb.setLength(sb.length()-3);
        sb.append(")\n");
        //过滤字段
        sb.append(" |> filter(fn: (r) => ");
        for(String key : params.getKeys().split(",")){
            sb.append(" r[\"_field\"] == \""+key+"\" or");
        }
        sb.setLength(sb.length()-3);
        sb.append(")\n");
        //过滤传感器
        sb.append(" |> filter(fn: (r) => r[\"di\"] == \""+terminal.getCode()+"\")\n");
        if(!StrUtil.equals("NONE",params.getAgg())){
            sb.append(" |> aggregateWindow(every: "+(params.getInterval()/1000)+"s, fn: "+getAgg(params.getAgg())+", createEmpty: false)\n");
        }
        sb.append(" |> yield(name: \"mean\")");
        InfluxDBClient client = influxdbServer.getClient();
        QueryApi queryApi = client.getQueryApi();
        List<FluxTable> tables = queryApi.query(sb.toString());
        Map<String,List<Map<String,Object>>> result = new HashMap<>();
        for(FluxTable table :tables){
            for(FluxRecord record :table.getRecords()){
                Long time =  Objects.requireNonNull(record.getTime()).getEpochSecond();
                List<Map<String, Object>> rows = null;
                if(result.containsKey(record.getField())){
                    rows = result.get(record.getField());
                }else{
                    rows = new ArrayList<>();
                    result.put(record.getField(),rows);
                }
                Map<String,Object> row = new HashMap<>();
                rows.add(row);
                row.put("ts",time);
                //保留2位小数
                if(record.getValue()==null){
                    row.put("value",record.getValue());
                } else if(record.getValue().getClass()==Double.class){
                    BigDecimal bd = new BigDecimal(record.getValue().toString());
                    row.put("value",bd.setScale(2, RoundingMode.HALF_UP).doubleValue());
                }else if(record.getValue().getClass()==Float.class){
                    BigDecimal bd = new BigDecimal(record.getValue().toString());
                    row.put("value",bd.setScale(2, RoundingMode.HALF_UP).floatValue());
                } else{
                    row.put("value",record.getValue());
                }
            }
        }

        return R.ok(result);
    }


    //MIN, MAX, AVG, SUM, COUNT, NONE
    String getAgg(String str){
        if(StrUtil.equals("MIN",str)){
            return "min";
        }else if(StrUtil.equals("MAX",str)){
            return "max";
        }else if(StrUtil.equals("SUM",str)){
            return "sum";
        }else if(StrUtil.equals("COUNT",str)){
            return "count";
        }else {
            return "mean";
        }
    }



    public void validSign(){
        Context context = Context.current();
        String timestamp = context.header("timestamp");
        String sign = context.header("sign");
        if(StrUtil.hasEmpty(timestamp,sign)){
            throw new ServiceException("签名参数错误");
        }
        long ts = 0;
        try{
            ts = Long.parseLong(timestamp);
        }catch (Exception e){
            throw new ServiceException("签名参数错误");
        }
        if(Math.abs((ts - System.currentTimeMillis())/1000)>300){
            throw new ServiceException("签名时间错误");
        }

        JSONObject jsonObject = configService.getConfig(SysConfigService.Keys.token);
        String token = jsonObject.getString("value");
        if(!StrUtil.equals(sign,DigestUtil.md5Hex(token+timestamp))){
            throw new ServiceException("签名错误");
        }
    }


    @Data
    public static class SignalQueryParams {
        String keys;
        Long startTs;
        Long endTs;
        String intervalType;
        Long interval;
        String timeZone;
        String limit;
        String agg;
        String orderBy;
        String terminalCode;
        Boolean useStrictDataTypes;
    }
}
